<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script id="ColorVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    void main(){
        vec4 worldPosi = u_ModelM4 * a_Position;
        gl_Position = u_PvM4 * worldPosi;
    }

</script>
<script id="ColorFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;
    void main(){
        gl_FragColor = u_Color;
    }
</script>
<script id="StrokeVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    attribute vec3 a_Normal;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    uniform float u_StrokeLen;
    void main(){
        vec4 worldPosi = u_ModelM4 * a_Position;
        vec3 normal = normalize(a_Normal);
        worldPosi = vec4(normal * u_StrokeLen, 0.0) + worldPosi;
        gl_Position = u_PvM4 * worldPosi;
    }

</script>
<script id="StrokeFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;
    void main(){
        gl_FragColor = u_Color;
    }
</script>
<script type="module">
    import { Matrix4, Vector3, Color, OrthographicCamera, PerspectiveCamera, Spherical } from '../js/three.module.js';
    import { CreateProgram, imgPromise } from '../js/Utils.js';
    import lib from '../js/ThreeLib.js';
    import CameraController from '../js/CameraController.js'
    import Poly from '../js/Poly4.js'
    import Control from '../js/Control.js'
    import Square from '../js/objs/Square.js'
    import Sphere from '../js/objs/Sphere.js'

    import Geo from '../js/Poly2/Geo.js'
    import Mat from '../js/Poly2/Mat.js'
    import Obj3D from '../js/Poly2/Obj3D.js'
    import Scene from '../js/Poly2/Scene.js'
    import Frame from '../js/Poly2/Frame.js'



    //#region gl
    const canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight - 4;

    const gl = canvas.getContext("webgl", { antialias: true, });

    //gl.enable(gl.BLEND);
    //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);//去图片描边
    //gl.enable(gl.DEPTH_TEST);
    //#endregion

    const scene = new Scene({ gl: gl });
    scene.addAxisProgram();
    scene.regProgram('color', {
        program: CreateProgram(gl,
            document.querySelector('#ColorVS').innerText,
            document.querySelector('#ColorFS').innerText),
        attribs: ['a_Normal', 'a_Position'],
        uniforms: ['u_PvM4', 'u_ModelM4', 'u_Color']
    });
    scene.regProgram('stroke', {
        program: CreateProgram(gl,
            document.querySelector('#StrokeVS').innerText,
            document.querySelector('#StrokeFS').innerText),
        attribs: ['a_Normal', 'a_Position'],
        uniforms: ['u_PvM4', 'u_ModelM4', 'u_Color', 'u_StrokeLen']
    });



    const pvM4 = new Matrix4();
    const modelM4 = new Matrix4();
    const eye = new Vector3(2, 2, 2);
    const lookAt = new Vector3();

    const camera = new PerspectiveCamera(45, canvas.width / canvas.height, 1, 20);
    const cameraController = new CameraController({
        canvas,
        camera,
        pvMatrix: pvM4,
        eye,
        lookAt,
    });


    const axis = new Obj3D().createAxis('axis');
    //scene.add(axis);

    const square = new Square(0.1);
    const [squareVertices, squareElements, squareNormals] = [
        square.vertices, square.elements, square.normals
    ];

    const sphere = new Sphere(0.1, 10, 10);
    const [sphereVertices, sphereElements, sphereNormals] = [
        sphere.vertices, sphere.elements, sphere.normals
    ];

    const strokeLen = 0.02;
    let spheres = [];
    let curStroke = null;//当前的描边对象

    //main
    {
        //先插入, 不然描边会挡住本体
        scene.drawObjs.set('stroke', []);

        //创建10个不同位置的正形体
        spheres = createRandomPosi(10);


    }



    let square3D = new Obj3D({
        geo: new Geo({
            data: {
                'a_Position': { size: 3, array: new Float32Array(squareVertices) },
            },
            element: { array: new Uint16Array(squareElements) }
        }),
        mat: new Mat({
            program: 'color',
            mode: ['TRIANGLES'],
            data: {
                'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                'u_ModelM4': { type: 'uniformMatrix4fv', value: modelM4.elements },
                'u_Color': { type: 'uniform4fv', value: [1, 1, 1, 1] }
            },
        })
    });
    let squareStroke = new Obj3D({
        geo: new Geo({
            data: {
                'a_Position': square3D.geo.data['a_Position'],
                'a_Normal': { size: 3, array: new Float32Array(squareNormals) }
            },
            element: square3D.geo.element
        }),
        mat: new Mat({
            program: 'stroke',
            mode: ['TRIANGLES'],
            data: {
                'u_PvM4': square3D.mat.data['u_PvM4'],
                'u_ModelM4': square3D.mat.data['u_ModelM4'],
                'u_Color': { type: 'uniform4fv', value: [0, 1, 1, 1] },
                'u_StrokeLen': { type: 'uniform1f', value: strokeLen },
            },
        })
    });

    // square3D.depthTest = false;
    // squareStroke.depthTest = false;

    // squareStroke.add(gl, square3D);
    // scene.add(squareStroke);



    //检测鼠标是否进入四方体
    canvas.addEventListener('mousemove', function (e) {
        //当前指针在世界坐标的位置
        const { clientX, clientY } = e;
        const curr = getCurrWorldPosi(clientX, clientY);

        //记录选择的对象
        const inObj3D = [];
        for (let i = 0; i < spheres.length; i++) {
            //获取三角形集合
            const obj = spheres[i];
            const triangles = getTriangles(obj.geo.data['a_Position'].array, obj.geo.element.array);
            //检测是否在里面
            const m = isInTriangle(triangles, curr);
            if (m != null) {
                inObj3D.push({ obj: obj, m: m });
            }
        }
        //判断集合中哪个在最上面
        if (inObj3D.length > 0) {

            let curInd = 0;
            let m = inObj3D[curInd].m;
            let min = eye.clone().distanceTo(m);
            for (let i = 1; i < inObj3D.length; i++) {
                m = inObj3D[i].m;
                const dist = eye.clone().distanceTo(m);
                if (min > dist) {
                    min = dist;
                    curInd = i;
                }
            }
            let obj = inObj3D[curInd].obj;

            //获取到最近的对象
            //给这个对象套上描边
            const stroke = createStroke(obj);
            stroke.add(gl, obj);
            //关闭深度测试
            stroke.depthTest = false;

            //删除
            if (curStroke != null) {
                scene.remove(curStroke);
                scene.add(curStroke.children[0]);
            }
            curStroke = stroke;
            scene.remove(obj);
            scene.add(stroke);

        } else {
            if (curStroke != null) {
                scene.remove(curStroke);
                scene.add(curStroke.children[0]);
                curStroke = null;
            }
        }
    })

    function createStroke(obj3D) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': obj3D.geo.data['a_Position'],
                    'a_Normal': { size: 3, array: new Float32Array(squareNormals) }
                },
                element: obj3D.geo.element
            }),
            mat: new Mat({
                program: 'stroke',
                mode: ['TRIANGLES'],
                data: {
                    'u_PvM4': obj3D.mat.data['u_PvM4'],
                    'u_ModelM4': obj3D.mat.data['u_ModelM4'],
                    'u_Color': { type: 'uniform4fv', value: [0, 1, 1, 1] },
                    'u_StrokeLen': { type: 'uniform1f', value: strokeLen },
                },
            })
        });
    }

    function createRandomPosi(count = 10) {
        const objArr = [];
        for (let i = 0; i < count; i++) {
            const random = [Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1];
            const obj = createSquare(random);
            objArr.push(obj);
            scene.add(obj)
        }
        return objArr;
    }
    function createSquare(randomArr) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array(allTransform(squareVertices, new Matrix4().setPosition(...randomArr))) },
                },
                element: { array: new Uint16Array(squareElements) }
            }),
            mat: new Mat({
                program: 'color',
                mode: ['TRIANGLES'],
                data: {
                    'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: new Matrix4().elements },
                    'u_Color': { type: 'uniform4fv', value: [...randomArr, 1] }
                },
            })
        });
    }
    function allTransform(vertices, mm4) {
        const verticesCur = [];
        for (let i = 0; i < vertices.length; i += 3) {
            const cur = new Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);
            verticesCur.push(...cur.clone().applyMatrix4(mm4));
        }
        return verticesCur;
    }


    function isInTriangle(triangles, curr) {
        for (let i = 0; i < triangles.length; i++) {
            const ret = inTriangle(triangles[i][0], triangles[i][1], triangles[i][2], curr);
            if (ret != null) {
                return ret;
            }
        }
        return null;
    }
    function inTriangle(A, B, C, curr) {
        const e = eye;
        const n = A.clone().sub(B).cross(B.clone().sub(C));
        const v = curr.clone().sub(e);
        // (M-A) · n = 0;
        // v入 = (M - E)
        // M = v入 + E
        // (v入 + E - A) · n = 0;
        // (v入 · n) + (E - A) · n = 0;
        // v入 · n = (A - E) · n
        // 入 = (A - E) · n / v · n
        // ((A - E) · n / v · n) * v + E
        const M = v.clone().multiplyScalar((A.clone().sub(e).dot(n) / v.clone().dot(n))).add(e);
        //判断是否在里面
        const node = [A, B, C];
        for (let i = 0; i < 3; i++) {
            const node1 = node[i];
            const node2 = node[(i + 1) % 3];
            const m = M;
            const MA = node1.clone().sub(m);
            const AB = node2.clone().sub(node1);
            const MF = MA.clone().cross(AB);

            if (MF.dot(n) < 0) {
                return null;
            }
        }
        return M;
    }
    function getTriangles(vertices, elements) {
        const triangles = [];
        for (let i = 0; i < elements.length; i += 3) {
            const node = [elements[i], elements[i + 1], elements[i + 2]];
            triangles.push([
                new Vector3(vertices[node[0] * 3], vertices[node[0] * 3 + 1], vertices[node[0] * 3 + 2]),
                new Vector3(vertices[node[1] * 3], vertices[node[1] * 3 + 1], vertices[node[1] * 3 + 2]),
                new Vector3(vertices[node[2] * 3], vertices[node[2] * 3 + 1], vertices[node[2] * 3 + 2]),
            ]);
        }
        return triangles;
    }

    function getCurrWorldPosi(x, y) {
        const hx = canvas.width / 2;
        const hy = canvas.height / 2;
        const curr = new Vector3((x - hx) / hx, -(y - hy) / hy, 0);
        return curr.applyMatrix4(pvM4.clone().invert());
    }

    (function ani() {
        draw();
        requestAnimationFrame(ani)
    })();


    function draw() {
        scene.setUniform('u_PvM4', { value: pvM4.elements });

        scene.draw([0.5, 0.5, 0.5, 1]);
    }
</script>

</html>